package com.bubblegumproject.ogo.component;

import com.bubblegumproject.ogo.OgoException;
import com.bubblegumproject.ogo.action.Context;
import com.bubblegumproject.ogo.component.injection.ContextDependencyInjector;
import com.bubblegumproject.ogo.component.injection.DependencyInjectorChain;
import com.bubblegumproject.ogo.tx.CurrentTransactionDependencyInjector;
import org.apache.log4j.Logger;

import javax.swing.event.EventListenerList;
import java.util.Collection;
import java.util.LinkedHashMap;

/**
 * A basic implementation of the {@link Container} interface.
 * <p/>
 * XXX: Note that there's a big problem with this class. All the methods are synchronized. This is not good since
 * every AbstractFlow relies on the Container to start() and destroy() components needed to do work. We need to rewrite this
 * class so it's heavily threadsafe
 *
 * @author Azubuko Obele (buko.obele@gmail.com)
 */
public class SimpleContainer implements Container {

    /**
     * Store all ComponentProxies managed by the Container. Note that, for dependency resolution purposes, the
     * order in which Components are registered inside a Container does matter.
     */
    private LinkedHashMap<Class, ComponentProxy> proxyMap = new LinkedHashMap<Class, ComponentProxy>();

    protected EventListenerList listenerList = new EventListenerList();

    protected DependencyInjectorChain dependencyInjector = createDependencyInjectorChain();

    private static final Logger log = Logger.getLogger(SimpleContainer.class);

    public synchronized <T> void register(Class<T> componentClazz) {
        assert (componentClazz != null);
        if (proxyMap.containsKey(componentClazz)) {
            throw new IllegalArgumentException(this + " already registered class " + componentClazz);
        }

        // remove this check until we have a better understanding of annotations and inheritance
        // Component metadata = componentClazz.getAnnotation(Component.class);
        // if (metadata == null) throw new IllegalArgumentException(componentClazz + " is not a valid Component class");

        ComponentProxy<T> proxy = new ComponentProxy<T>(componentClazz, dependencyInjector);
        proxyMap.put(componentClazz, proxy);

        log.debug(this + " registered proxy " + proxy + " for class " + componentClazz);
    }

    public boolean contains(Class clazz) {
        return findProxy(clazz) != null;
    }

    public synchronized <T> T create(Class<T> targetClazz, Context context) {
        assert (targetClazz != null);

        ComponentProxy<T> proxy = findProxy(targetClazz);

        if (proxy == null) {
            throw new IllegalArgumentException(this + " cannot create Component for targetClazz=" + targetClazz +
                    " because no ComponentProxy exists for it!");
        }

        try {
            T component = proxy.create();
            proxy.start(component, this, context);
            return component;
        } catch (Throwable t) {
            throw new OgoException(this + " can't start Component " + targetClazz, t);
        }
    }

    public synchronized void destroy(Object component) {
        try {
            getProxy(getRealComponentClass(component)).stop(component, this);
        } catch (Throwable t) {
            throw new OgoException(this + " failed to start component " + component, t);
        }
    }

    /**
     * @return proxies for all registered components in the order they were registered
     */
    protected Collection<ComponentProxy> getComponentProxies() {
        return proxyMap.values();
    }

    protected <T> ComponentProxy<T> findProxy(Class<T> clazz) {
        assert (clazz != null);
        ComponentProxy proxy = proxyMap.get(clazz);

        if (proxy == null) {
            // look for the first Proxy that can satisfy the given contract
            for (ComponentProxy componentProxy : proxyMap.values()) {
                if (clazz.isAssignableFrom(componentProxy.getComponentClass())) {
                    proxy = componentProxy;
                }
            }
        }

        return proxy;
    }

    public synchronized <T> ComponentProxy<T> getProxy(Class<T> componentClazz) {
        assert (componentClazz != null);
        ComponentProxy<T> proxy = proxyMap.get(componentClazz);
        if (proxy != null) {
            return proxy;
        } else {
            throw new IllegalArgumentException(this + " doesn't contain proxy for class " + componentClazz);
        }
    }

    public <T> ComponentProxy<T> getProxy(Object component) {
        return getProxy(getRealComponentClass(component));
    }

    protected Class getRealComponentClass(Object component) {
        // since this is a cglib subclass let's take the superclass. in the future we might look for Class in the
        // hierarchy that contains a certain annotation
        return component.getClass().getSuperclass();
    }

    protected DependencyInjectorChain createDependencyInjectorChain() {
        DependencyInjectorChain chain = new DependencyInjectorChain(
                new ContextDependencyInjector(), new CurrentTransactionDependencyInjector());
        return chain;
    }

    public String toString() {
        return getClass().getSimpleName() + "[proxyMap=" + proxyMap + "]";
    }
}
